Skip to main content

Refresh Token

POST/api/v1/users/auth/refresh-token

Rotates the supplied refresh token within its family and issues a new 15-minute access token plus a new refresh token. Replay of an already-rotated token revokes the entire family. Sets Cache-Control: no-store.

cv-api-key + refresh_token (body)
Productionhttps://api.care360-next.carevalidate.com/api/v1/users/auth/refresh-token
Staginghttps://api-staging.care360-next.carevalidate.com/api/v1/users/auth/refresh-token
note

This endpoint does not take an Authorization: Bearer header. It authenticates via the refresh_token field in the request body. The field name is snake_case per RFC 6749 §6.

Headers

Headers
cv-api-keystringrequired

Your unique API key for authentication.

Content-Typestringrequired

Must be application/json.

Request Body

Body
refresh_tokenstringrequired

The opaque refresh token previously returned by /verify-otp or a prior /refresh-token call.

Behavior

  1. SHA3-512-hashes the presented refresh token and looks up the row.
  2. Verifies the row's organizationId matches the org resolved from cv-api-key. Mismatch → REFRESH_INVALID.
  3. If the row was already revoked (revokedAt !== null), revokes the entire family by familyId and rejects with REFRESH_REUSED. This is the reuse-detection trigger: a previously-rotated token has been replayed.
  4. Checks sliding (expiresAt) and absolute (absoluteExpiresAt) expiry.
  5. Re-resolves the user and OrganizationAccess role.
  6. Generates a new opaque token, persists it within the same family (rotation), and mints a new access JWT.

The previously presented refresh token is now invalid; only the newly returned one will work next time.

Example Request

curl -X POST '<BASE_URL>/api/v1/users/auth/refresh-token' \
-H 'cv-api-key: <redacted>' \
-H 'Content-Type: application/json' \
-d '{
"refresh_token": "<opaque-refresh-token>"
}'

Responses

200SuccessRefresh token rotated. Returns new access + refresh tokens. Response includes Cache-Control: no-store.
{
"status": 200,
"success": true,
"accessToken": "<new JWT, HS512>",
"expiresIn": 900,
"refreshToken": "<new opaque token>",
"refreshTokenExpiresAt": "2026-05-29T12:00:00.000Z",
"patientId": "550e8400-e29b-41d4-a716-446655440000"
}
400Validation errorcv-api-key missing or body fails Zod (refresh_token empty).
{
"status": 400,
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR"
}
404Organization not foundcv-api-key does not resolve to a partner organization.
{
"status": 404,
"success": false,
"error": "Organization not found",
"code": "NOT_FOUND"
}
401Refresh token not recognizedToken hash not found in the database.
{
"status": 401,
"success": false,
"error": "Refresh token not recognized",
"code": "REFRESH_INVALID"
}
401Refresh token does not belong to this organizationToken's organizationId does not match the cv-api-key org.
{
"status": 401,
"success": false,
"error": "Refresh token does not belong to this organization",
"code": "REFRESH_INVALID"
}
401User no longer existsThe owning user has been deleted.
{
"status": 401,
"success": false,
"error": "User no longer exists",
"code": "REFRESH_INVALID"
}
401Refresh token has already been used (REUSE)Token was already rotated. The entire family has been revoked. Treat as terminal — wipe local tokens and force a fresh /send-otp.
{
"status": 401,
"success": false,
"error": "Refresh token has already been used",
"code": "REFRESH_REUSED"
}
401Refresh token has expiredSliding 30-day window elapsed.
{
"status": 401,
"success": false,
"error": "Refresh token has expired",
"code": "REFRESH_EXPIRED"
}
401Refresh token absolute lifetime exceeded90-day cap from family creation reached.
{
"status": 401,
"success": false,
"error": "Refresh token absolute lifetime exceeded",
"code": "REFRESH_ABSOLUTE_EXPIRED"
}
caution

A REFRESH_REUSED response is terminal. Wipe local tokens and require the user to start over with /send-otp. Do not retry.

Try It Out